%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%  连接器
%%%  客户端(可以是游戏客户端或是GM工具客户端等)在系统中的一个影射,
%%%  负责收发数据，用来控制角色进程或其它进程
%%% @end
%%% Created : 21. 6月 2021 9:22
%%%-------------------------------------------------------------------
-module(server_conn).
-author("liuwentao").
-export([
    create/4
    ,send/2
    ,pack/2
    ,pack_send/2
    ,pack_send/3

]
).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-include("common.hrl").
-include("conn.hrl").

-define(MAX_LEN, 524288). %% 限制最大的单条协议长度


%% ----------------------------------------------------
%% 对外接口
%% ----------------------------------------------------

%% @doc 创建一个连接器
-spec create(ClientType, Socket, Ip, Port) -> {ok, pid()} | ignore | {error, term()} when
    ClientType :: monitor | game | tester,
    Socket :: port(),
    Ip :: tuple(),
    Port :: pos_integer().
create(ClientType, Socket, Ip, Port) ->
    gen_server:start(?MODULE, [ClientType, Socket, Ip, Port], []).

%% @doc 通知连接器发送数据
%% <div>当此函数在角色进程内执行时会自动处理发送缓冲区的操作</div>
%% <div>如果不是在角色进程内执行则会直接将数据发送到客户端</div>
-spec send(pid(), binary()) -> ok.
send(ConnPid, Bin) ->
    case get(send_buff) of
        undefined -> ConnPid ! {send_data, Bin};
        [] -> ConnPid ! {send_data, Bin};
        [H] -> put(send_buff, [[Bin | H]]);
        [H | T] -> put(send_buff, [[Bin | H] | T])
    end.

send(ConnPid, Bin, Code) ->
    case get(send_buff) of
        undefined -> ConnPid ! {send_data, Bin, Code};
        [] -> ConnPid ! {send_data, Bin, Code};
        [H] -> put(send_buff, [[Bin | H]]);
        [H | T] -> put(send_buff, [[Bin | H] | T])
    end.

%% @doc 打包协议数据
-spec pack(pos_integer(), tuple()) -> {ok, binary()} | {false, bitstring()}.
pack(Code, Data) ->
    case mapping:module(game_server, Code) of
        {ok, _Auth, _Caller, Parser, _ModName} ->
            case catch Parser:pack(srv, Code, Data) of
                {ok, Bin} -> {ok, Bin};
                _Err -> {false, ?T("打包数据时发生异常")}
            end;
        {error, _Code} ->
            {false, ?T("打包数据时发生异常")}
    end.

%% @doc 给当前角色打包并发送消息(在角色进程内调用才有效)
-spec pack_send(pos_integer(), tuple()) -> ok.
pack_send(Code, Data) ->
    case get(conn_pid) of
        undefined -> ?ERR("无法在非角色进程内调用: ~w", [Code]);
        ConnPid -> pack_send(ConnPid, Code, Data)
    end.

%% @doc 打包并发送消息
-spec pack_send(pid(), pos_integer(), tuple()) -> ok.
pack_send(ConnPid, Code, Data) when is_pid(ConnPid)->
    case mapping:module(game_server, Code) of
        {ok, _Auth, _Caller, Parser, _ModName} ->
            case catch Parser:pack(srv, Code, Data) of
                {ok, Bin} ->
                    send(ConnPid, Bin, Code);
                Err ->
                    ?ERR("打包数据出错[Code: ~w][Err: ~p][Data: ~w][stacktrace:~w]", [Code, Err, Data, util:get_stacktrace()])
            end;
        {error, _Code} ->
            ?ERR("模块影射失败[~w]:~w", [Code, Data])
    end;
pack_send(_ConnPid, _Code, _Data) ->
    skip.

%% ----------------------------------------------------
%% 内部处理
%% ----------------------------------------------------

init([ClientType, Socket, Ip, Port]) ->
    process_flag(trap_exit, true),
    self() ! read_next,
    erlang:send_after(60000, self(), client_check),
    erlang:send_after(300000, self(), account_check),
    erlang:send_after(10000, self(), loop),
    State = #conn{type = ClientType, socket = Socket, ip = Ip, port = Port, connect_time = time()},
    ?DEBUG("[~p:~p]建立连接成功：~w", [Ip, Port, self()]),
    {ok, State}.

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

%% 客户端状态检查，如果客户端一定时间内不发送指令则认定为已断线
handle_info(client_check, State = #conn{account = _Account, recv_count = RecvCount, last_recv_count = LastRecvCount}) ->
    case RecvCount > LastRecvCount of
        true ->
            erlang:send_after(60000, self(), client_check),
            {noreply, State#conn{last_recv_count = RecvCount}};
        false ->
            ?ERR("[~ts]的客户端长时间未发送指令，可能已经断线", [_Account]),
            {stop, normal, State}
    end;

handle_info(read_next, State) ->
    read_next(State);

%% 检查是否已经验证了身份
handle_info(account_check, State = #conn{account = Account, ip = Ip}) when Account =:= "" ->
    ?ERR("客户端[~w]连接后长时间未登录帐号，强制断开连接", [Ip]),
    {stop, normal, State};

%% 发送socket数据
handle_info({send_data, Bin}, State = #conn{account = Account, socket = Socket, send_count = SendCount, error_send = ErrSend}) ->
    case catch erlang:port_command(Socket, Bin) of
        true ->
            {noreply, State#conn{send_count = SendCount + 1}};
        false ->
            ?ERR("帐号[~ts]执行port_command失败", [Account]),
            {noreply, State#conn{error_send = ErrSend + 1}};
        Else ->
            ?ERR("帐号[~ts]发送socket数据失败:~w", [Account, Else]),
            {noreply, State#conn{error_send = ErrSend + 1}}
    end;

%% 发送socket数据
handle_info({send_data, Bin, Code}, State = #conn{account = Account, socket = Socket, send_count = SendCount, error_send = ErrSend}) ->
    case catch erlang:port_command(Socket, Bin) of
        true ->
            ?INFO("帐号[~ts:~p] send code[~p]", [Account, self(), Code]),
            {noreply, State#conn{send_count = SendCount + 1}};
        false ->
            ?ERR("帐号[~ts]执行port_command失败", [Account]),
            {noreply, State#conn{error_send = ErrSend + 1}};
        Else ->
            ?ERR("帐号[~ts]发送socket数据失败:~w", [Account, Else]),
            {noreply, State#conn{error_send = ErrSend + 1}}
    end;

%% -------------------------------------------------------------------
%% 处理socket数据读取结果
%% -------------------------------------------------------------------

%% 客户端断开了连接
handle_info({inet_async, _Socket, _Ref, {error, closed}}, State = #conn{account = _Account}) ->
    ?DEBUG("在服务端接收数据时，帐号[~ts]关闭了socket连接", [_Account]),
    {stop, normal, State};

%% 收到包头数据，检查长度，如果太长的数据则不接受
handle_info({inet_async, _Socket, _Ref, {ok, <<Len:32/little>>}}, State = #conn{account = Account, read_head = true}) when Len > ?MAX_LEN ->
    ?ERR("帐号[~ts]发送的socket数据过长: ~w", [Account, Len]),
    {stop, normal, State};
%% 收到包头数据
handle_info({inet_async, Socket, _Ref, {ok, <<Len:32/little>>}}, State = #conn{read_head = true}) ->
    prim_inet:async_recv(Socket, Len, 80000), %% 读取内容
    {noreply, State#conn{length = Len, read_head = false}};

%% 收到正常数据
handle_info({inet_async, _Socket, _Ref, {ok, <<Code:16/little, Bin/binary>>}}, State = #conn{account = _Account, read_head = false}) ->
    routing(Code, Bin, State);
%% 收到异常数据
handle_info({inet_async, _Socket, _Ref, {ok, _Bin}}, State = #conn{bad_req_count = BadReq, account = Account, ip = IP}) when BadReq > 10 ->
    %% 最多记录10个错误，不要全部记录，避免被攻击
    ?INFO("客户端[ACC:~ts, IP:~w]发送了过多无效请求,断开连接", [Account, IP]),
    {stop, normal, State};
%% {noreply, State#conn{bad_req_count = BadReq + 1}};
handle_info({inet_async, Socket, _Ref, {ok, Bin}}, State = #conn{account = Account, ip = Ip, socket = Socket, bad_req_count = BadReq}) ->
    ?ERR("客户端[Acc:~ts IP:~w]发送了无效请求: ~w", [Account, Ip, Bin]),
    {noreply, State#conn{bad_req_count = BadReq + 1}};
%% 接收socket数据时发生了未预料的错误
handle_info({inet_async, _Socket, _Ref, {error, _Reason}}, State = #conn{account = Account}) ->
    case _Reason of
        timeout -> ?DEBUG("帐号[~ts]读取socket数据出错:~w", [Account, _Reason]);
        _ -> ?ERR("帐号[~ts]读取socket数据出错:~w", [Account, _Reason])
    end,
    {stop, normal, State};

%% 处理socket数据发送结果
handle_info({inet_reply, _Socket, ok}, State) ->
    {noreply, State};
handle_info({inet_reply, _Socket, {error, closed}}, State = #conn{account = _Account}) ->
    ?DEBUG("在服务端发送数据时，帐号[~ts]关闭了socket连接", [_Account]),
    {stop, normal, State};
handle_info({inet_reply, _Socket, {error, timeout}}, State = #conn{error_send = ErrSend}) ->
    {noreply, State#conn{error_send = ErrSend + 1}};
handle_info({inet_reply, _Socket, _Else}, State = #conn{account = _Account}) ->
    ?ERR("帐号[~ts]发送socket数据时发生了未预料的错误: ~w", [_Account, _Else]),
    {stop, normal, State};

%% 信息发送接收监控开关
handle_info({debug, Debug}, State = #conn{account = Account}) when Debug =:= true orelse Debug =:= false ->
    ?INFO("账号[~ts]监控开关切换到：~w", [Account, Debug]),
    put(debug, Debug),
    {noreply, State};

handle_info({'EXIT', Pid, normal}, State = #conn{pid_object = Pid}) ->
    {stop, normal, State};

%% 处理关联进程异常退出
handle_info({'EXIT', Pid, Why}, State = #conn{account = Account, pid_object = ObjectPid}) when Pid =:= ObjectPid ->
    ?ERR("帐号[~ts]控制的目标进程[~w]异常退出:~w", [Account, Pid, Why]),
    {stop, normal, State};

%% 内部循环
handle_info(loop, State = #conn{loop_counter = C, pid_object = _ObjectPid}) ->
    %% 约每隔180秒执行一次GC
    case C rem 18 =:= 0 of
        false -> ignore;
        true -> garbage_collect()
    end,
    %% 10秒后进行下次循环
    erlang:send_after(10000, self(), loop),
    {noreply, State#conn{loop_counter = C + 1}};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, #conn{account = _Account, socket = Socket, object = _Object, pid_object = _ObjectPid}) ->
    catch gen_tcp:close(Socket),
    ?DEBUG("帐号[~ts]的连接进程已退出", [_Account]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% ----------------------------------------------------
%% 私有函数
%% ----------------------------------------------------

%% 路由处理
routing(Code, Bin, State = #conn{account = Account, recv_count= RecvCount, bad_req_count = BadReq}) ->
    case mapping:module(game_server, Code) of
        %% 处理需要验证的模块，只是检查account是否为空
        {ok, true, Caller, Parser, Mod} when Account =/= "" ->
            call(Caller, Parser, Mod, Code, Bin, State#conn{recv_count = RecvCount + 1});
        {ok, false, Caller, Parser, Mod} ->
            call(Caller, Parser, Mod, Code, Bin, State#conn{recv_count = RecvCount + 1});
        _Else -> %% 匹配失败，有可能是非法客户端发的错误数据
            ?ERR("帐号[~ts]发送了无效请求[~w]", [Account, Code]),
            read_next(State#conn{bad_req_count = BadReq + 1})
    end.

%% 通知连接器读取下一条指令
read_next(State = #conn{socket = Socket, recv_count = RecvCount, read_head = false}) ->
    prim_inet:async_recv(Socket, 4, 60000),
    {noreply, State#conn{recv_count = RecvCount + 1, read_head = true}};
read_next(State) ->
    %% 上一个数据包还未读取完成，忽略掉
    {noreply, State}.

%% 由connector执行调用
call(connector, Parser, Mod, Code, Bin, State = #conn{account = Account, recv_count = RecvCount, socket = Socket, pid_object = ObjectPid}) ->
    case catch Parser:unpack(srv, Code, Bin) of
        {ok, Data} ->

%%            %% --------------------------------------------------------------------------------
%%            %% ----------------协议执行效率统计--------------------
%%            %% --------------------------------------------------------------------------------
%%            HandleResult = case catch timer:tc(Mod, handle, [Code, Data, State#conn{recv_count = RecvCount + 1}]) of
%%                               {T, V} when is_integer(T) ->	rank:add_pt(Code, T),
%%                                   V;
%%                               E ->							E
%%                           end,
%%            case HandleResult of
                %% --------------------------------------------------------------------------------
                %%                 <  测试期间打开,正式则需要注释掉,顺便开启下面一行的case  >
                %% --------------------------------------------------------------------------------
             case catch Mod:handle(Code, Data, State#conn{recv_count = RecvCount + 1}) of
                ok ->
                    read_next(State);
                {ok, NewState} when is_record(NewState, conn) ->
                    read_next(NewState);
                {reply, Reply} ->
                    pack_send(self(), Code, Reply),
                    read_next(State);
                {reply, Reply, NewState} when is_record(NewState, conn) ->
                    pack_send(self(), Code, Reply),
                    read_next(NewState);
                stop when is_pid(ObjectPid) ->
                    ObjectPid ! normal_exit,
                    {stop, normal, State};
                stop ->
                    {stop, normal, State};
                {stop, Reason} ->
                    case pack(1080, {Reason}) of
                        {ok, Bin1} -> catch gen_tcp:send(Socket, Bin1);
                        _ -> ignore
                    end,
                    {stop, normal, State};
                {error, _Reason} ->
                    ?ERR("帐号[~ts]的连接器在处理命令时出错:~w [Code:~w Data:~w]", [Account, _Reason, Code, Data]),
                    read_next(State);
                _Reason ->
                    ?ERR("帐号[~ts]的连接器在处理命令时发生了未预料的错误[Mod:~w Code:~w Data:~w Reason:~w]", [Account, Mod, Code, Data, _Reason]),
                    {noreply, State}
            end;
        _Err ->
            ?ERR("解包数据出错[Mod:~w Code:~w Err:~w]:~w", [Mod, Code, _Err, Bin]),
            read_next(State)
    end;

%% 过滤无效请求
call(object, _Parser, _Mod, _Code, _Data, State = #conn{pid_object = undefined}) -> read_next(State);
call(object, _Parser, _Mod, _Code, _Data, State = #conn{object = undefined}) -> read_next(State);

%% 由object执行调用
call(object, Parser, Mod, Code, Bin, State = #conn{object = Object, pid_object = ObjectPid}) ->
    Object:rpc(ObjectPid, Parser, Mod, Code, Bin),
    {noreply, State}. %% 注意:此处不能直接read_next，必须等调用者通知后才读取下一个包，否则容易被攻击
